﻿using Microsoft.VisualStudio.TestTools.UnitTesting;
using System;
using System.IO;
using System.Collections.Generic;
using Liquifier;

namespace LiquifierTest
{
    public partial class LiquiferAttributesTest
    {

        #region Basic Reference Integrity Tests

        [Liquifiable]
        public class RefTestBasic
        {
            public RefTestBasic one;
            public RefTestBasic two;
        }

        [TestMethod()]
        public void Maintains_Top_Level_Liquifiable_References()
        {
            RefTestBasic before = new RefTestBasic();

            RefTestBasic inner = new RefTestBasic();

            before.one = inner;
            before.two = inner;

            RefTestBasic after = DoLiquifify<RefTestBasic>(before);

            Assert.AreSame(after.one, after.two);
        }

        [TestMethod()]
        public void Maintains_MultiLevel_Liquifiable_References()
        {
            RefTestBasic before = new RefTestBasic();

            RefTestBasic level_one = new RefTestBasic();
            RefTestBasic level_two = new RefTestBasic();
            RefTestBasic level_three = new RefTestBasic();

            before.one = level_one;
            before.two = level_two;
            level_two.one = level_one;
            level_one.one = level_two;
            level_one.two = level_three;
            level_two.two = level_three;

            RefTestBasic after = DoLiquifify<RefTestBasic>(before);

            Assert.AreSame(after.one, after.two.one);
            Assert.AreSame(after.one.two, after.two.two);
        }

        [TestMethod()]
        public void Maintains_Circular_References()
        {
            RefTestBasic before = new RefTestBasic();

            RefTestBasic level_one = new RefTestBasic();

            before.one = level_one;
            level_one.one = before;
            before.two = before;
            level_one.two = level_one;

            RefTestBasic after = DoLiquifify<RefTestBasic>(before);

            Assert.AreSame(after, after.one.one);
            Assert.AreSame(after.two, after);
            Assert.AreSame(after.one, after.one.two);
        }


        #endregion

        #region Advanced Reference Integrity Tests

        [Liquifiable]
        public class AdvRefTest
        {
            public List<AdvRefTest> list = new List<AdvRefTest>();
            public ObservableList<AdvRefTest> obvlist = new ObservableList<AdvRefTest>();
            public List<List<AdvRefTest>> listOfList = new List<List<AdvRefTest>>();
            public AdvRefTest[] array = new AdvRefTest[10];
            public AdvRefTest[][] arrayOfArray = new AdvRefTest[5][];
            public AdvRefTest[][][] arrayOfArrayOfArray = new AdvRefTest[2][][];

            public AdvRefTest item;
        }

        [TestMethod()]
        public void Maintains_References_Within_Arrays()
        {
            AdvRefTest before = new AdvRefTest();

            AdvRefTest item = new AdvRefTest();
            before.item = item;

            before.list.Add(item);
            before.array[0] = item;
            before.array[1] = item;

            before.obvlist.Add(item);

            AdvRefTest after = DoLiquifify<AdvRefTest>(before);

            Assert.AreSame(after.item, after.list[0]);
            Assert.AreSame(after.item, after.array[0]);
            Assert.AreSame(after.item, after.array[1]);
            Assert.AreSame(after.item, after.obvlist[0]);
        }

        [TestMethod()]
        public void Maintains_References_Of_Arrays_Themselves()
        {
            AdvRefTest before = new AdvRefTest();

            AdvRefTest item = new AdvRefTest();

            before.listOfList.Add(before.list);
            before.list.Add(item);

            before.array[0] = before.list[0];

            before.arrayOfArray[0] = before.array;

            before.arrayOfArrayOfArray[0] = before.arrayOfArray;
            before.arrayOfArrayOfArray[1] = new AdvRefTest[1][];

            AdvRefTest after = DoLiquifify<AdvRefTest>(before);

            Assert.AreSame(after.list, after.listOfList[0]);        //List<> is acctually processed as a liquifiable object as opposed to a ICollection
            Assert.AreSame(after.list[0], after.listOfList[0][0]);

            Assert.AreSame(after.array, after.arrayOfArray[0]);
            Assert.AreSame(after.array[0], after.arrayOfArray[0][0]);

            Assert.AreSame(after.arrayOfArrayOfArray[0], after.arrayOfArray);
        }


        [TestMethod()]
        public void MaintainsCircularReferencesOfArraysAndListObjects()
        {
            Assert.Fail("Not implemented");
        }

        #endregion

        #region Special Types Integrity Tests

        [Liquifiable]
        public class RefTestDictionary
        {
            public Dictionary<object, object> dictionary1 = new Dictionary<object, object>();
            public Dictionary<object, object> dictionary2 = new Dictionary<object, object>();
        }

        [TestMethod()]
        public void Maintains_Reference_Integrity_With_Dictionary()
        {
            RefTestDictionary before = new RefTestDictionary();

            object key1 = new object();
            object value1 = new object();

            object key2 = new object();
            object value2 = new object();

            //Part 1 - make sure that when an object is present in both dictionaries, it remains the same object in both deserialised dictionaries. 
            //Make sure this is true for keys and values

            before.dictionary1.Add(key1, value1);
            before.dictionary1.Add(key2, value2);

            before.dictionary2.Add(key1, value1);
            before.dictionary2.Add(key2, value2);

            //Part 2 - same as above but for value type keys

            before.dictionary1.Add(10, value1);
            before.dictionary2.Add(10, value1);

            //Part 3 - make sure that references to the dictionary itself can be maintained, within other dictionaries

            before.dictionary1.Add(20, before.dictionary2);

            //Part 4 = make sure this is true of circular references

            before.dictionary2.Add(20, before.dictionary1);

            var after = DoLiquifify<RefTestDictionary>(before);

            //Part 1 verification

            foreach (var k in after.dictionary1.Keys)
            {
                var dictionary1value = after.dictionary1[k];

                Assert.IsTrue(after.dictionary2.ContainsKey(k));
                Assert.AreSame(dictionary1value, after.dictionary2[k]);
            }

            //Part 2 verification

            Assert.AreSame(after.dictionary1[10], after.dictionary2[10]);

            //Part 3 verification

            Assert.AreSame(after.dictionary2, after.dictionary1[20]);

            //Part 4 verification

            Assert.AreSame(after.dictionary1, after.dictionary2[20]);

        }



        [Liquifiable]
        public class RefTestQueue
        {
            public object object1;
            public object object2;

            public Queue<int> integerQ = new Queue<int>();
            public Queue<object> objectQ = new Queue<object>();
        }

        [TestMethod()]
        public void Maintains_Reference_Integrity_With_Queue()
        {
            RefTestQueue before = new RefTestQueue();

            //Part 1 - are queues with value types supported correctly

            before.integerQ.Enqueue(0);
            before.integerQ.Enqueue(1);
            before.integerQ.Enqueue(2);

            //Part 2 - are queues with reference types supported correctly, and are references preserved

            before.object1 = new object();
            before.object2 = new object();

            before.objectQ.Enqueue(before.object1);
            before.objectQ.Enqueue(before.object2);

            //Part 3 - are queues with circular references supported correctly

            before.objectQ.Enqueue(before.objectQ);

            RefTestQueue after = DoLiquifify<RefTestQueue>(before);

            //Part 1 - Verification

            int i;
            i = after.integerQ.Dequeue();
            Assert.AreEqual(0, i);
            i = after.integerQ.Dequeue();
            Assert.AreEqual(1, i);
            i = after.integerQ.Dequeue();
            Assert.AreEqual(2, i);

            //Part 2 - verification

            object d1 = after.objectQ.Dequeue();
            object d2 = after.objectQ.Dequeue();

            Assert.AreSame(after.object1, d1);
            Assert.AreSame(after.object2, d2);

            //Part 3 - verification

            object d3 = after.objectQ.Dequeue();

            Assert.AreSame(after.objectQ, d3);
        }

        #endregion
    }
}
